/*
 * Copyright (C) 2010 Graham Allan
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package edu.strath.cis.grallan.findbugs.adaptiveranking.analysis;

import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import edu.strath.cis.grallan.findbugs.adaptiveranking.AdaptiveRankBug;
import edu.strath.cis.grallan.findbugs.adaptiveranking.AdaptiveRankedBugList;
import edu.strath.cis.grallan.findbugs.adaptiveranking.UserDesignationToPositivesMap;
import edu.strath.cis.grallan.findbugs.adaptiveranking.UserDesignationToPositivesMap.Positive;
import edu.strath.cis.grallan.findbugs.adaptiveranking.arm.ARMRank;
import edu.strath.cis.grallan.findbugs.adaptiveranking.arm.ARMRanker;
import edu.strath.cis.grallan.findbugs.adaptiveranking.lr.LearningRank;
import edu.strath.cis.grallan.findbugs.adaptiveranking.lr.LearningValueRanker;
import edu.strath.cis.grallan.findbugs.adaptiveranking.population.PopulationExtractor;
import edu.umd.cs.findbugs.BugCollection;
import edu.umd.cs.findbugs.BugInstance;
import edu.umd.cs.findbugs.Project;
import edu.umd.cs.findbugs.cloud.Cloud.UserDesignation;
import edu.umd.cs.findbugs.gui2.BugLeafNode;
import edu.umd.cs.findbugs.gui2.BugLoader;
import edu.umd.cs.findbugs.gui2.BugSet;
import edu.umd.cs.findbugs.gui2.IApplicationState;
import edu.umd.cs.findbugs.gui2.sort.Sortable;
import edu.umd.cs.findbugs.gui2.sort.Sortables;

/**
 * @author Graham Allan (grallan@cis.strath.ac.uk)
 */
public class CumulativeTruePositiveWriter {

	private final String baseDir;
	private final String unclassifiedProjectFileName;
	private final String classifiedProjectFileName;

	public CumulativeTruePositiveWriter(String baseDir, String unclassifiedProjectFileName,
			String classifiedProjectFileName) {
		this.baseDir = baseDir;
		this.unclassifiedProjectFileName = unclassifiedProjectFileName;
		this.classifiedProjectFileName = classifiedProjectFileName;

	}

	public void doWrite() throws Exception {
		System.out.printf("Writing results for %s.%n", classifiedProjectFileName);
		writeOptimal();
		writeCumulativeTruePositivesFromDefaultGUI2Order();
		writeCumulativeTruePositivesFromARMRankMode();
		writeCumulativeTruePositivesFromLearningMode();
		writeAllAsCSV();
	}

	@SuppressWarnings("unchecked")
	private void writeAllAsCSV() throws Exception {
		List<String> optimal = readCumulativeFromFile("optimal.order");
		List<String> defaultOrder = readCumulativeFromFile("gui2-default.order");
		List<String> arm = readCumulativeFromFile("armRank.order");
		List<String> learning = readCumulativeFromFile("learning.order");

		checkAllSameSize(optimal, defaultOrder, arm, learning);

		FileWriter writer = new FileWriter(baseDir + "all.order");
		writer.write(String.format("%s,%s,%s,%s,%s%n", "inspected", "optimal", "default", "arm", "learning"));
		for (int i = 0; i < optimal.size(); i++) {
			String inspected = String.valueOf(i);
			String csvValues = String.format("%s,%s,%s,%s,%s%n", inspected, optimal.get(i), defaultOrder.get(i), arm.get(i), learning
					.get(i));
			writer.write(csvValues);
		}
		writer.close();
		System.out.println("Finished writing all together as CSV.");
	}

	private void checkAllSameSize(List<String>... lists) {
		int expectedSize = lists[0].size();
		for (List<String> l : lists) {
			assert l.size() == expectedSize : "Lists should all be the same size.";
		}
	}

	private List<String> readCumulativeFromFile(String fileName) throws FileNotFoundException, IOException {
		List<String> optimal = new ArrayList<String>();
		BufferedReader reader = new BufferedReader(new FileReader(baseDir + fileName));
		String value = null;
		while ((value = reader.readLine()) != null) {
			optimal.add(value);
		}
		return optimal;
	}

	private void writeOptimal() {
		BugCollection classifiedBugCollection = loadBugCollection(baseDir + classifiedProjectFileName);
		BugCollection unclassifiedBugCollection = loadBugCollection(baseDir + unclassifiedProjectFileName);
		IApplicationState appState = makeApplicationState(unclassifiedBugCollection);

		int numberOfTruePositives = 0;
		for (BugInstance bug : classifiedBugCollection) {
			if (new UserDesignationToPositivesMap().bugHasThisDesignation(bug, Positive.TRUE_POSITIVE)) {
				numberOfTruePositives++;
			}
		}

		int totalNumberOfBugs = classifiedBugCollection.getCollection().size();
		List<String> optimalCumulative = new ArrayList<String>(totalNumberOfBugs);
		for (int i = 1; i <= totalNumberOfBugs; i++) {
			int cumulativeNumber = i > numberOfTruePositives ? numberOfTruePositives : i;
			optimalCumulative.add(Integer.toString(cumulativeNumber));
		}

		ClassificationOrderMonitor.createAndWriteFromList(optimalCumulative, appState, "optimal");
		System.out.println("Finished writing for optimal order.");
	}

	private void writeCumulativeTruePositivesFromLearningMode() {

		BugCollection classifiedBugCollection = loadBugCollection(baseDir + classifiedProjectFileName);
		BugCollection unclassifiedBugCollection = loadBugCollection(baseDir + unclassifiedProjectFileName);
		IApplicationState appState = makeApplicationState(unclassifiedBugCollection);

		ClassificationOrderMonitor classificationMonitor = new ClassificationOrderMonitor(appState, "learning");

		PopulationExtractor extractor = PopulationExtractor.instanceFor(unclassifiedBugCollection);
		LearningValueRanker learningRanker = new LearningValueRanker(appState, extractor);
		ARMRanker armRanker = new ARMRanker(appState, extractor);

		while (bugsLeftToClassify(unclassifiedBugCollection)) {

			armRanker.computeRankOrder();
			AdaptiveRankedBugList<ARMRank> armRankList = armRanker.getRankedList();

			ARMRank topRanked = (ARMRank) firstUnclassified(armRankList);
			while (topRanked != null && topRanked.rank() > 0.0d) {
				doDesignateBug(classifiedBugCollection, unclassifiedBugCollection, classificationMonitor, topRanked);

				topRanked = (ARMRank) firstUnclassified(armRankList);
			}

			learningRanker.computeRankOrder();
			AdaptiveRankedBugList<LearningRank> learningList = learningRanker.getRankedList();
			LearningRank bugLearningRank = (LearningRank) firstUnclassified(learningList);

			if (bugLearningRank != null) {
				doDesignateBug(classifiedBugCollection, unclassifiedBugCollection, classificationMonitor,
						bugLearningRank);
			}

		}
		System.out.println("Finished writing for learning mode.");
	}

	private void writeCumulativeTruePositivesFromARMRankMode() {
		BugCollection classifiedBugCollection = loadBugCollection(baseDir + classifiedProjectFileName);
		BugCollection unclassifiedBugCollection = loadBugCollection(baseDir + unclassifiedProjectFileName);
		IApplicationState appState = makeApplicationState(unclassifiedBugCollection);

		ClassificationOrderMonitor classificationMonitor = new ClassificationOrderMonitor(appState, "armRank");

		PopulationExtractor extractor = PopulationExtractor.instanceFor(unclassifiedBugCollection);
		ARMRanker armRanker = new ARMRanker(appState, extractor);

		while (bugsLeftToClassify(unclassifiedBugCollection)) {

			armRanker.computeRankOrder();
			AdaptiveRankedBugList<ARMRank> armRankList = armRanker.getRankedList();

			ARMRank topRanked = (ARMRank) firstUnclassified(armRankList);
			if (topRanked != null) {
				doDesignateBug(classifiedBugCollection, unclassifiedBugCollection, classificationMonitor, topRanked);
			}
		}
		System.out.println("Finished writing for ARM-RANK mode.");
	}

	private void writeCumulativeTruePositivesFromDefaultGUI2Order() {
		BugCollection classifiedBugCollection = loadBugCollection(baseDir + classifiedProjectFileName);
		BugCollection unclassifiedBugCollection = loadBugCollection(baseDir + unclassifiedProjectFileName);
		IApplicationState appState = makeApplicationState(unclassifiedBugCollection);

		ClassificationOrderMonitor classificationMonitor = new ClassificationOrderMonitor(appState, "gui2-default");

		BugSet bugSet = new BugSet(unclassifiedBugCollection, null);

		List<Sortable> defaultOrder = Arrays.<Sortable> asList(Sortables.CATEGORY, Sortables.BUGCODE, Sortables.TYPE,
				Sortables.BUG_RANK);
		Iterable<BugLeafNode> copyBugsAndSortBy = bugSet.copyBugsAndSortBy(defaultOrder);

		while (bugsLeftToClassify(unclassifiedBugCollection)) {
			BugInstance topRanked = firstUnclassified(copyBugsAndSortBy);

			if (topRanked != null) {
				doDesignateBug(classifiedBugCollection, unclassifiedBugCollection, classificationMonitor, topRanked);
			}
		}
		System.out.println("Finished writing for Default Order.");

	}

	private IApplicationState makeApplicationState(BugCollection unclassifiedBugCollection) {
		// A new use for Mockito!
		IApplicationState appState = mock(IApplicationState.class);
		when(appState.bugCollection()).thenReturn(unclassifiedBugCollection);
		when(appState.getSaveFile()).thenReturn(new File(baseDir + unclassifiedProjectFileName));
		return appState;
	}

	private void doDesignateBug(BugCollection classifiedBugCollection, BugCollection unclassifiedBugCollection,
			ClassificationOrderMonitor classificationMonitor, AdaptiveRankBug<?> topRanked) {
		String designation = getDesignation(topRanked.bug(), classifiedBugCollection);
		topRanked.bug().setUserDesignationKey(designation, unclassifiedBugCollection);
//		System.out.println("Designating as " + designation + " [rank: " + topRanked.rank() + ", bug: "
//				+ topRanked.bug() + "]");
		classificationMonitor.notifyBugDesignated(topRanked.bug());
	}

	private static void doDesignateBug(BugCollection classifiedBugCollection, BugCollection unclassifiedBugCollection,
			ClassificationOrderMonitor classificationMonitor, BugInstance topRanked) {
		String designation = getDesignation(topRanked, classifiedBugCollection);
		topRanked.setUserDesignationKey(designation, unclassifiedBugCollection);
//		System.out.println("Designating as " + designation + " [bug: " + topRanked + "]");
		classificationMonitor.notifyBugDesignated(topRanked);
	}

	private static AdaptiveRankBug<?> firstUnclassified(AdaptiveRankedBugList<?> learningList) {
		for (AdaptiveRankBug<?> rank : learningList) {
			if (UserDesignation.UNCLASSIFIED.name().equalsIgnoreCase(rank.bug().getUserDesignationKey())) {
				return rank;
			}
		}
		return null;
	}

	private static BugInstance firstUnclassified(Iterable<BugLeafNode> bugSet) {
		for (BugLeafNode bugLeafNode : bugSet) {
			if (UserDesignation.UNCLASSIFIED.name().equalsIgnoreCase(bugLeafNode.getBug().getUserDesignationKey())) {
				return bugLeafNode.getBug();
			}
		}
		return null;
	}

	private static String getDesignation(BugInstance topRanked, BugCollection classifiedBugCollection) {

		for (BugInstance bug : classifiedBugCollection) {
			if (bug.compareTo(topRanked) == 0) {
				return bug.getUserDesignationKey();
			}
		}

		throw new RuntimeException("A bug was found that wasn't classified.");
	}

	private static boolean bugsLeftToClassify(BugCollection unclassifiedBugCollection) {
		for (BugInstance bug : unclassifiedBugCollection) {
			if (UserDesignation.UNCLASSIFIED.name().equalsIgnoreCase(bug.getUserDesignationKey())) {
				return true;
			}
		}
		return false;
	}

	private static BugCollection loadBugCollection(String fileName) {
		File classifiedProjectFile = new File(fileName);
		Project classifiedProject = BugLoader.loadProject(null, classifiedProjectFile);
		BugCollection classifiedBugCollection = BugLoader.loadBugs(null, classifiedProject, classifiedProjectFile);
		return classifiedBugCollection;
	}

}
